home *** CD-ROM | disk | FTP | other *** search
- /* Rush.c
-
- COPYRIGHT:
- Copyright Denis Pelli, 1997. This file may be distributed freely as long
- as this notice accompanies it and any changes are noted in the source.
- It is distributed as is, without any warranty implied or provided. We
- accept no liability for any damage or loss resulting from the use of
- this software.
-
- PURPOSE:
- Run any critical user function with minimal interruption by Mac OS
- system software. Without Rush, the Mac OS and device drivers steal
- chunks of time whenever they like. E.g. the Zip disk driver, when no
- disk is inserted, steals 2 ms once every 3 seconds. For a loop showing a
- real-time movie, the possibility of losing 2 milliseconds, out of a
- frame time of say 13 ms, substantially reduces the maximum number of
- pixels that can be shown per frame if we insist on never missing a
- frame. Rush implements two alternate solutions. Apple's guidelines
- suggest that driver interrupt tasks should be very brief, passing any
- lengthy tasks to the Deferred Task Manager to run later. When
- priorityLevel is -1, Rush runs the user code as a deferred task, which
- runs normally, except that all other deferred tasks are blocked until
- our code finishes. When priorityLevel is 0 the user code runs normally.
- When priorityLevel is >0 the user code runs at that raised processor
- priority. The Deferred Task Manager doesn't run at all until the
- processor priority goes back to zero. Thus any nonzero priorityLevel
- will block all deferred tasks. A positive priorityLevel will also block
- some primary interrupt tasks; more are blocked the higher the
- priorityLevel. At priorityLevel 7 nearly all interrupts are blocked.
-
- When priorityLevel is nonzero we block all the Mac's deferred tasks
- while the user function is run. When priorityLevel is 1 (or more) we
- block deferred tasks by raising the priority before calling the user
- function. When priorityLevel is -1 we block deferred tasks by calling
- the user function from within a deferred task.
-
- The deferred-task solution is attractive. All deferred tasks are blocked
- until ours finishes, and all urgent interrupt processes (including VBL
- and Time Manager updates) occur normally. And keyboard and mouse still
- work. This elegant solution was suggested by Bo3b Johnson at Apple
- Developer Support on 4/19/97 (Follow-up: 418098). A limitation is
- that we can't use the 68k fpu in the deferred task, because we
- don't save & restore the fpu registers. (Apple advises against it.)
-
- The raised-priority solution has a drawback. Raising priority (to 1 or
- more) does block the deferred tasks, but also results in abnormal
- operation of the Time Manager (overflow and coarse steps) and freezes
- the mouse and keyboard.
-
- 4/22/97 Testing with MATLAB LoopTest.m indicates that Rush.mex runs fine
- at all priorities. However, a 2 ms interruption due to the Zip driver
- (when no disk is inserted) occurs about one every three seconds at
- priorities -1, 0, and 1, and only disappears at priorities 2 and higher.
- Contrary to Apple's rules, the Zip driver's 2 ms interruption is NOT a
- deferred task. We're hoping that Iomega will fix their driver soon. For
- news you might look here:
- <http://www.macintouch.com/jazprobs.html>
-
- RESTRICTIONS:
- When the code is compiled to use the 68881 floating point unit (or the
- equivalent fpu built into the high-end 680x0 processor) and the
- priorityLevel is -1, the user function supplied to Rush should not use
- floating point. This is because the user function will run at interrupt
- time, and our code below doesn't save or restore the floating point
- registers. Apple suggests this approach, banning use of the floating
- point unit at interrupt time, because saving and restoring the 68k
- floating point registers is slow. Actually, in this particular
- application that delay would be acceptable, because it wouldn't affect
- the running speed, only the set up time, but I don't in fact know how to
- save and restore the registers. Apple's information on this presumed
- fluency with 68K assembly, which I lack.
-
- EXAMPLE 1:
- Let's rush the calculation of c=a+b. (This simple example is solely for
- the sake of exposition. I can't think of any reason to rush this. In
- real life you'll only want to rush code that must synchronize with
- external events, e.g. displaying or recording.)
-
- typedef struct {
- int a,b,c;
- } Abc;
-
- void main(void)
- {
- Abc abc;
- int priorityLevel=7;
-
- abc.a=a;
- abc.b=b;
- Rush(priorityLevel,&Summer,&abc);
- c=abc.c;
- }
-
- void Summer(void *argPtr)
- {
- Abc *abcPtr;
-
- abcPtr=argPtr;
- abcPtr->c = abcPtr->a + abcPtr->b;
- }
-
- EXAMPLE 2:
- This example illustrates something closer to the use for which Rush.c
- was created. Here we call back to the main application (i.e. MATLAB) to
- rush high-level code. The C subroutine called "mexFunction" is a MEX
- function, callable from MATLAB programs, e.g. Rush('c=a+b'). The
- mexFunction receives a string of MATLAB code, e.g. "c=a+b". Our rushed C
- routine, fun(), asks the MATLAB application to use "eval" to execute the
- string, which has the effect of rushing this bit of MATLAB code. Again,
- in real life you'll only want to rush code that must synchronize with
- external events. We now Rush all our MATLAB display loops.
-
- typedef struct{
- mxArray *mxString;
- long error;
- } Stuff;
-
- void mexFunction(int nlhs, mxArray *plhs[], int nrhs, CONSTmxArray *prhs[])
- {
- Stuff stuff;
-
- stuff.mxString=prhs[0];
- stuff.error=0;
- Rush(priorityLevel,&fun,&stuff);
- }
-
- void fun(void *argPtr)
- {
- Stuff *stuffPtr;
- mxArray *plhs[1],*prhs[1];
-
- stuffPtr=(Stuff *)argPtr;
- prhs[0]=stuffPtr->mxString;
- mexSetTrapFlag(1);
- stuffPtr->error=mexCallMATLAB(0,plhs,1,prhs,"eval");
- mexSetTrapFlag(0);
- }
-
- HISTORY:
- 4/21/97 dgp Wrote it as a MEX file.
- 4/22/97 dgp Polished the code. Marked the done flag as "volatile",
- since it's set by the deferred task at interrupt time.
- 5/2/97 dgp Fixed bug in definition of GetA1 declaration to fix 68K crash reported by dhb.
- 6/22/97 dgp Updated comments above.
- 7/1/97 dgp As requested by Josh Solomon, removed the mex-specific stuff to leave this
- generic version that i'm adding to the VideoToolbox. Josh wants to use
- it to create a RUSH facility in Mathematica, like the one I created in MATLAB.
- KNOWN BUGS:
- */
- #include <VideoToolbox.h>
- #ifndef __PROCESSES__
- #include <Processes.h> // ProcessSerialNumber
- #endif
- #ifndef __TRAPS__
- #include <Traps.h> // _DTInstall
- #endif
- #define CODE_RESOURCE MATLAB
- #if GENERATING68K
- #pragma parameter __D0 GetA1()
- long GetA1(void)= 0x2009; // MOVE.L A1,D0
- #pragma parameter __D0 GetA4()
- long GetA4(void)= 0x200C; // MOVE.L A4,D0
- #pragma parameter __D0 GetA5()
- long GetA5(void)= 0x200D; // MOVE.L A5,D0
- #if THINK_C
- #pragma parameter __D0 SetA4(__D0)
- long SetA4(long) = 0xC18C; // EXG D0,A4
- #else
- long SetA4(long:__D0):__D0 = 0xC18C; // EXG D0,A4
- #endif
- #else
- #define GetA1() 0L
- #define GetA4() 0L
- #define GetA5() 0L
- #define SetA4(x) 0L
- #endif
- typedef struct{
- ProcessSerialNumber psn;
- DeferredTask *deferredTaskPtr;
- volatile long A,error,failedAttempts;
- void (*functionPtr)(void *argPtr);
- void *argPtr;
- volatile Boolean done;
- } Stuff;
- #if GENERATINGPOWERPC
- static void OurDeferredTask(register Stuff *stuffPtr);
- #else
- static void OurDeferredTask(void);
- #endif
- static void CallFunction(Stuff *stuffPtr);
-
- int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr);
-
- int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr)
- {
- static Boolean firstTime=1,deferAvailable;
- static DeferredTaskUPP deferredTaskUPP;
- int oldPriority;
- DeferredTask deferredTask;
- int error;
- Stuff stuff;
-
- if(firstTime){
- deferAvailable=TrapAvailable(_DTInstall);
- deferredTaskUPP=NewDeferredTaskProc(OurDeferredTask);
- firstTime=0;
- }
- if(priorityLevel<-1 || priorityLevel>7)PrintfExit("%s: Illegal priorityLevel %d.",__FILE__,priorityLevel);
- error=GetCurrentProcess(&stuff.psn);
- stuff.functionPtr=functionPtr;
- stuff.argPtr=argPtr;
- stuff.done=0;
- stuff.error=0;
- stuff.failedAttempts=0;
- stuff.deferredTaskPtr=NULL;
- switch(priorityLevel){
- case -1:
- // Run user's function as a deferred task.
- if(!deferAvailable)PrintfExit("%s: Your System is too old: no Deferred Task Manager.",__FILE__);
- if(GetPriority()>0){
- SetPriority(0);
- PrintfExit("%s: Can't defer task while processor priority is above zero.",__FILE__);
- }
- #if CODE_RESOURCE
- stuff.A=GetA4();
- #else
- stuff.A=GetA5();
- #endif
- stuff.deferredTaskPtr=&deferredTask;
- deferredTask.qType=dtQType;
- deferredTask.dtAddr=deferredTaskUPP;
- deferredTask.dtReserved=0;
- deferredTask.dtParam=(long)&stuff;
- error=DTInstall(stuff.deferredTaskPtr);
- if(error)PrintfExit("%s: DTInstall error %d.",__FILE__,error);
- while(!stuff.done) ; // wait until our deferredTask is done
- break;
- case 0:
- // Run user's function normally.
- CallFunction(&stuff);
- break;
- default:
- // Run user's function at raised processor priority.
- oldPriority=GetPriority();
- SetPriority(priorityLevel);
- CallFunction(&stuff);
- SetPriority(oldPriority);
- break;
- }
- if(stuff.error==1234)
- PrintfExit("%s: user function never ran; always interrupted wrong process.",__FILE__);
- if(stuff.failedAttempts)
- printf("%s: WARNING: user function ran only after %ld attempts that interrupted other processes.\n",__FILE__,stuff.failedAttempts);
- return stuff.failedAttempts;
- }
-
- #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
- #pragma options(!profile) // it would be dangerous to call the profiler from here
- #pragma options(assign_registers,redundant_loads)
- #endif
- #if __MWERKS__ && __profile__
- #pragma profile off // on 68k it would be dangerous to call the profiler from here
- #endif
-
- #if GENERATINGPOWERPC
- static void OurDeferredTask(register Stuff *stuffPtr)
- {
- CallFunction(stuffPtr);
- }
- #else
- static void OurDeferredTask(void)
- {
- Stuff *stuffPtr;
- long oldA;
-
- stuffPtr=(void *)GetA1();
- #if CODE_RESOURCE
- oldA=SetA4(stuffPtr->A);
- #else
- oldA=SetA5(stuffPtr->A);
- #endif
- CallFunction(stuffPtr);
- #if CODE_RESOURCE
- SetA4(oldA);
- #else
- SetA5(oldA);
- #endif
- }
- #endif
-
- static void CallFunction(Stuff *stuffPtr)
- {
- ProcessSerialNumber psn;
- Boolean isOurs;
- int error;
-
- error=GetCurrentProcess(&psn);
- error=SameProcess(&stuffPtr->psn,&psn,&isOurs); // compare psns.
- if(isOurs){
- // It's safe. Let's do it.
- (*stuffPtr->functionPtr)(stuffPtr->argPtr);
- stuffPtr->error=0;
- stuffPtr->done=1;
- }else{
- // Oops. We're interrupting some other process. It's unsafe to run now.
- // Let's lie low, and reinstall ourselves to try again later.
- stuffPtr->failedAttempts++;
- if(stuffPtr->failedAttempts<10 && stuffPtr->deferredTaskPtr!=NULL)
- stuffPtr->error=DTInstall(stuffPtr->deferredTaskPtr);
- else stuffPtr->error=1234;
- if(stuffPtr->error)stuffPtr->done=1; // Can't do it; give up.
- }
- }
-
-